TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import type { Metadata } from 'next';2import { notFound } from 'next/navigation';3import { requireUser } from '@/lib/auth/session';4import { getCollectionDetail } from '@/lib/account/queries';5import { getDisplay } from '@/lib/account/display';6import { confidenceLabel, fmtDate } from '@/lib/format';7import { PrintButton } from './print-button';89export const metadata: Metadata = { title: 'Insurance schedule', robots: { index: false } };1011/** Printable schedule of items with values, sources and confidence — a document, not an appraisal. */12export default async function InsurancePage({ params }: { params: Promise<{ id: string }> }) {13 const { id } = await params;14 const u = await requireUser(`/collections/${id}/insurance`);15 const detail = await getCollectionDetail(u.id, id);16 if (!detail) notFound();17 const d = await getDisplay();18 const { collection: c, summary: s, items } = detail;19 const rows = [...s.items].sort((a, b) => (b.valueUsd ?? 0) - (a.valueUsd ?? 0));20 return (21 <div className="mx-auto max-w-4xl print:max-w-none">22 <div className="mb-4 flex items-center justify-between print:hidden">23 <p className="text-sm text-muted">Use your browser's print dialog to save as PDF.</p>24 <PrintButton />25 </div>26 <article className="card p-8 print:border-0 print:p-0 print:shadow-none">27 <header className="flex items-start justify-between border-b border-border pb-4">28 <div>29 <p className="text-[11px] font-semibold uppercase tracking-wider text-subtle">RareIndex · Collection schedule</p>30 <h1 className="mt-1 text-2xl font-semibold tracking-tight">{c.name}</h1>31 <p className="text-sm text-muted">32 Prepared for {u.name ?? u.email} · {fmtDate(new Date())} · values in {d.currency}33 </p>34 </div>35 <div className="text-right">36 <p className="text-[11px] uppercase tracking-wider text-subtle">Total estimated value</p>37 <p className="num text-2xl font-semibold">{s.valuedCount ? d.money(s.valueUsd) : '—'}</p>38 <p className="text-xs text-muted">39 {s.valuedCount}/{s.itemCount} items valued · confidence {confidenceLabel(s.confidence)}40 </p>41 </div>42 </header>43 <table className="mt-4 w-full text-[12px]">44 <thead>45 <tr className="border-b border-border text-left text-[10px] uppercase tracking-wider text-subtle">46 <th className="py-1.5 pr-2">#</th>47 <th className="py-1.5 pr-2">Item</th>48 <th className="py-1.5 pr-2">Grade / condition</th>49 <th className="py-1.5 pr-2">Cert / serial</th>50 <th className="py-1.5 pr-2 text-right">Qty</th>51 <th className="py-1.5 pr-2 text-right">Acquired</th>52 <th className="py-1.5 pr-2 text-right">Cost</th>53 <th className="py-1.5 pr-2 text-right">Est. value</th>54 <th className="py-1.5 text-right">Basis</th>55 </tr>56 </thead>57 <tbody>58 {rows.map((i, idx) => {59 const src = items.find((x) => x.id === i.id)!;60 return (61 <tr key={i.id} className="border-b border-border align-top">62 <td className="py-1.5 pr-2 text-subtle">{idx + 1}</td>63 <td className="py-1.5 pr-2">64 <p className="font-medium">{i.title}</p>65 <p className="text-[11px] text-muted">{i.categorySlug.replace(/_/g, ' ')}{src.source ? ` · from ${src.source}` : ''}</p>66 </td>67 <td className="py-1.5 pr-2">{src.variantLabel ?? (i.grader ? `${i.grader.toUpperCase()} ${i.grade ?? ''}` : src.condition ?? '—')}</td>68 <td className="py-1.5 pr-2 font-mono text-[11px]">{[src.certificationNumber, src.serial].filter(Boolean).join(' / ') || '—'}</td>69 <td className="num py-1.5 pr-2 text-right">{i.quantity}</td>70 <td className="num py-1.5 pr-2 text-right">{i.acquiredAt ?? '—'}</td>71 <td className="num py-1.5 pr-2 text-right">{i.costUsd === null ? '—' : d.money(i.costUsd)}</td>72 <td className="num py-1.5 pr-2 text-right font-medium">{i.valueUsd === null ? 'unavailable' : d.money(i.valueUsd)}</td>73 <td className="py-1.5 text-right text-[11px] text-muted">{i.valueSource === 'manual' ? 'owner estimate' : i.valueSource === 'none' ? '—' : `RIV · ${confidenceLabel(i.confidence).toLowerCase()}`}</td>74 </tr>75 );76 })}77 </tbody>78 </table>79 <footer className="mt-6 text-[11px] leading-relaxed text-subtle">80 <p>81 RIV (RareIndex Valuation) figures are statistical estimates derived from observed public sales and listings, each with a confidence level and sample size; they are not appraisals, authentication or a guarantee of insurable value. Items marked "owner estimate" use a value entered by the collection owner. Items marked "unavailable" have insufficient market evidence. Historical purchase costs are converted to {d.currency} at the exchange rate of the acquisition date. RareIndex does not authenticate items.82 </p>83 </footer>84 </article>85 </div>86 );87}88